﻿using System;
using System.Threading.Tasks;
using BookSleeve;
using RedisDemos.Extensions;

namespace RedisDemos.Scenarios
{
    public class DistributeLock : Scenario
    {
        private readonly Int32 lockDurationInSecs = 10;
        private readonly Random rnd = new Random();

        protected override async Task Perform(RedisConnection conn)
        {
            StartExclusiveTask(conn, "tizio".ToUpper());
            StartExclusiveTask(conn, "caio".ToUpper(), die: true);
            StartExclusiveTask(conn, "sempronio".ToUpper());

            await Task.Delay(TimeSpan.FromSeconds(lockDurationInSecs * 3));
        }

        private void StartExclusiveTask(RedisConnection conn, String name, Boolean die = false)
        {
            Task.Factory.StartNew(async () =>
            {
                "{0} start.".LogInfo(name);
                var canOp = false;

                while (canOp == false)
                {
                    // SETNX key value 
                    canOp = await conn.Strings.SetIfNotExists(Db, "unsafeop:lock", name);
                    if (canOp)
                    {
                        "{0} lock taken.".LogInfo(name);

                        // EXPIRE key seconds 
                        await conn.Keys.Expire(Db, "unsafeop:lock", lockDurationInSecs);

                        "{0} do some unsafe operation.".LogInfo(name);
                        if (die)
                        {
                            "{0} dead, bye bye".LogInfo(name);
                            return;
                        }
                        
                        await Task.Delay(TimeSpan.FromSeconds(lockDurationInSecs / 2));
                        "{0} do some unsafe operation, done.".LogInfo(name);

                        // TTL key 
                        var ttl = await conn.Keys.TimeToLive(Db, "unsafeop:lock");
                        if (ttl > 1)
                            // DEL key [key ...] 
                            await conn.Keys.Remove(Db, "unsafeop:lock");
                    }
                    else
                    {
                        "{0} lock not taken, retry after few seconds.".LogInfo(name);
                        await Task.Delay(TimeSpan.FromSeconds(lockDurationInSecs / rnd.Next(2, 4)));
                    }
                }
            }, TaskCreationOptions.LongRunning);
        }
    }
}
